package io.summer.basis.tool;

import org.springframework.util.ClassUtils;

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author ml
 */
public final class LambdaTool {
  private static final Pattern PATTERN = Pattern.compile("^\\((.*)\\)(.*?);?$");

  /**
   * Prefix for internal array class names: {@code "["}.
   */
  private static final String INTERNAL_ARRAY_PREFIX = "[";

  /**
   * Prefix for internal non-primitive class names: {@code "L"}.
   */
  private static final String NON_PRIMITIVE_PREFIX = "L";

  private static final Map<String, Class<?>> JVM_CLASS_NAME_MAP;

  public static final int RETURN_TYPE = 0;

  static {
    JVM_CLASS_NAME_MAP = new HashMap<>(32);
    JVM_CLASS_NAME_MAP.put("B", byte.class);
    JVM_CLASS_NAME_MAP.put("S", short.class);
    JVM_CLASS_NAME_MAP.put("I", int.class);
    JVM_CLASS_NAME_MAP.put("J", long.class);
    JVM_CLASS_NAME_MAP.put("F", float.class);
    JVM_CLASS_NAME_MAP.put("D", double.class);
    JVM_CLASS_NAME_MAP.put("C", char.class);
    JVM_CLASS_NAME_MAP.put("Z", boolean.class);
    JVM_CLASS_NAME_MAP.put("V", void.class);
  }

  public static SerializedLambda getSerializedLambda(Serializable serializable) throws NoSuchMethodException
    , InvocationTargetException
    , IllegalAccessException {
    Method method = serializable.getClass().getDeclaredMethod("writeReplace");
    method.setAccessible(true);
    return (SerializedLambda) method.invoke(serializable);
  }

  private static String toJavaName(String jniName) {
    if (jniName.startsWith(INTERNAL_ARRAY_PREFIX)) {
      jniName += ";";
    } else if (jniName.startsWith(NON_PRIMITIVE_PREFIX)) {
      jniName = jniName.substring(1);
    } else {
      Class<?> cls = JVM_CLASS_NAME_MAP.get(jniName);
      jniName = cls != null ? cls.getName() : jniName;
    }
    return ClassUtils.convertResourcePathToClassName(jniName);
  }

  /**
   * 将一个jni 函数签名
   * ([Ljava/lang/String;)Lcom/xx/xxx/AppTest$TestDomain;
   * ([Ljava/lang/String;)V
   *
   * @param signature jni signature
   * @return string[0] return type, string[1:] input param.
   */
  public static String[] splitJniFunctionDescriptor(String signature) {
    Objects.requireNonNull(signature);
    Matcher m = PATTERN.matcher(signature);
    if (m.matches()) {
      String returnType = m.group(2);
      String params = m.group(1);
      String[] pp = params.split(";");
      String[] ret = new String[pp.length + 1];
      ret[RETURN_TYPE] = toJavaName(returnType);
      for (int i = RETURN_TYPE+1; i < pp.length + 1; i++) {
        ret[i] = toJavaName(pp[i - 1]);
      }
      return ret;
    } else {
      return new String[0];
    }
  }

  public static Class<?>[] getLambdaMethodType(Serializable serializable) throws NoSuchMethodException
    , IllegalAccessException, InvocationTargetException, ClassNotFoundException {
    SerializedLambda serializedLambda = getSerializedLambda(serializable);
    String[] types = splitJniFunctionDescriptor(serializedLambda.getInstantiatedMethodType());
    Class<?>[] classes = new Class<?>[types.length];
    for (int i = 0; i < types.length; i++) {
      classes[i] = ClassUtils.forName(types[i], null);
    }
    return classes;
  }
}
